﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ModelXML;
using Microsoft.Xna.Framework;

namespace Etapa1.Import.Anim
{
    public class Bone
    {
        private Bone(){}
        Matrix boneWorldTransform;
        Matrix poseInverse = Matrix.Identity;
        Matrix offset = Matrix.Identity;        
        Matrix final = Matrix.Identity;
        Matrix internalLocalOffset = Matrix.Identity;        
        Matrix pose;
        Matrix internalWorldOffset = Matrix.Identity;
        List<Bone> childs = new List<Bone>();
        int index;
        String name;
        Bone father;

        public Matrix InternalWorldOffset
        {
            get { return internalWorldOffset; }
            set { internalWorldOffset = value; }
        }

        public Matrix InternalLocalOffset
        {
            get { return internalLocalOffset; }
            set { internalLocalOffset = value; }
        }

        public Matrix WorldBoneMatrix
        {
            get { return final; }            
        }

        public Matrix BoneWorldTransform
        {
            get { return boneWorldTransform; }
            set { boneWorldTransform = value; }
        }        

        public List<Bone> Childs
        {
            get { return childs; }
            set { childs = value; }
        }        

        public Bone Father
        {
            get { return father; }
            set { father = value; }
        }        
        public String Name
        {
            get { return name; }
            set { name = value; }
        }
        
        public int Index
        {
            get { return index; }
            set { index = value; }
        }

        public static void BoneCount(Bone root, ref int number)
        {
            number = 0;
            CalculateBoneCount(root, ref number);
        }

        private static void CalculateBoneCount(Bone root, ref int number)
        {
            number++;
            foreach (var item in root.childs)
            {
                CalculateBoneCount(item, ref number);
            }
        }

        
        public static void GetNameBoneMapping(Bone Root, ref Dictionary<String, Bone> mapping)
        {
            if (mapping == null)
            {
                mapping = new Dictionary<string, Bone>();
            }
            mapping[Root.name] = Root;
            foreach (var item in Root.childs)
            {
                GetNameBoneMapping(item, ref mapping);
            }
        }

        public static Bone CreateBoneHierarchy(Modelo modelo, out Matrix[] BonesMatrix )
        {
            BonesMatrix = new Matrix[modelo.SkinInfo.Bones.Count];
            List<ModelXML.Bone> bones = modelo.SkinInfo.Bones;
            Bone bone = new Bone();
            ModelXML.Bone root = bones.Find((a) => String.IsNullOrEmpty(a.BoneParent));
            bone.father = null;
            bone.name = root.BoneName;
            bone.boneWorldTransform = ImporterHelper.CreateTransformation(root.BoneMatrix);
            bone.poseInverse = Matrix.Invert(ImporterHelper.CreateTransformation(root.BoneMatrix));
            bone.offset = ImporterHelper.CreateTransformation(root.BoneMatrix);
            bone.pose = ImporterHelper.CreateTransformation(root.BoneMatrix);

            int i = 0;
            bone.index = i++;
            BonesMatrix[bone.index] = bone.boneWorldTransform;
            foreach (var item in root.BoneChilds)
            {
                Bone createBoneHierarchy = CreateBoneHierarchy(bone, bones, item, ref i, BonesMatrix);
                bone.childs.Add(createBoneHierarchy);
            }
            return bone;
        }

        private static void BoneHierarchyToList(Bone root,  List<Bone> boneList)
        {
            boneList.Add(root);
            foreach (var item in root.childs)
            {
                BoneHierarchyToList(item, boneList);
            }
        }
        
        private static Bone CreateBoneHierarchy(Bone father, List<ModelXML.Bone> bones, String ChildName, ref int i, Matrix[] BonesMatrix)
        {
            ModelXML.Bone bonexml = bones.Find((a) => a.BoneName == ChildName);
            Bone bone = new Bone();
            bone.father = father;
            
            bone.poseInverse = Matrix.Invert(ImporterHelper.CreateTransformation(bonexml.BoneMatrix));
            bone.offset = Matrix.Invert(father.boneWorldTransform) * ImporterHelper.CreateTransformation(bonexml.BoneMatrix);
            // w2 = w1*l2 => INV(w1) * w2 = l2
            bone.boneWorldTransform = father.boneWorldTransform * bone.offset; ///eh soh o ImporterHelper.CreateTransformation(bonexml.BoneMatrix); ....
            bone.pose = ImporterHelper.CreateTransformation(bonexml.BoneMatrix);

            bone.name = bonexml.BoneName;
            bone.index = i++;
            BonesMatrix[bone.index] = bone.boneWorldTransform;
            foreach (var item in bonexml.BoneChilds)
            {
                bone.childs.Add(CreateBoneHierarchy(bone, bones, item, ref i, BonesMatrix));
            }
            return bone;
        }        
        
        public void Update(Matrix fatherTransform, ref Matrix[] FinalMatrix)
        {
            this.boneWorldTransform = fatherTransform * this.internalLocalOffset * this.offset;
            this.final = this.poseInverse * this.boneWorldTransform * this.internalWorldOffset;
            FinalMatrix[this.index] = this.final;

            foreach (var item in this.childs)
            {
                item.Update(this.boneWorldTransform, ref FinalMatrix);
            }
        }

    }
}
